import { effect } from "@vue/reactivity";
import { ShapeFlags } from "@vue/shared";
import { createAppApi } from "./apiCreateApp"
import { createComponentInstance, setupComponent } from "./component";
import { queueJob } from "./scheduler";
import { normalizeVNode, Text } from "./vnode";

/**
 * 渲染器，vue3 中用户可以重写不同的渲染器适配不同的平台
 * 创建一个渲染器
 */
export function createRender(renderOptions) {
  const {
    createElement: hostCreateElement,
    createText: hostCreateText,
    insert: hostInsert,
    patchProp: hostPatchProp,
    remove: hostRemove,
    setElementText: hostSetElementText,
    setText: hostSetText,
    nextSibling: hostNextSibling
  } = renderOptions;

  const setupRenderEffect = (instance, container) => {
    /*
    创建一个 effect 在 effect 中调用 render 方法，
    这样 render 方法中拿到的数据会收集找个 effect 依赖，属性更新就会重新执行
    */
    instance.update = effect(function componentEffect() { // 每个组件对应一个 effect， vue3 是组件级别更新，数据更新就会重新执行对应组件的 effect 函数
      if (!instance.isMounted) { // 初次渲染
        const proxyToUse = instance.proxy;
        // 执行用户编写 setup 返回的函数执行
        const subTree = instance.subTree = instance.render.call(proxyToUse, proxyToUse);

        // 初始化子树
        patch(null, subTree, container);

        instance.isMounted = true;
        // console.log('subTree', instance)
      } else { // 更新
        // 就产生 diff
        const prevTree = instance.subTree; // 表示旧节点
        let proxyToUse = instance.proxy;
        const nextTree = instance.render.call(proxyToUse, proxyToUse); // 表示新节点
        
        // console.log('更新');
        // console.log(prevTree, nextTree)
        patch(prevTree, nextTree, container);
      }
    }, {
      /*
        queueJob 解决这种多次更新的问题: 
        function setName () {
          state.name = 'JERRY';
          state.name = 'ddd';
          state.name = 'ccc';
        }
      */
      scheduler: queueJob
    })
  }

  //#region 处理组件
  const mountComponent = (initialVNode, container) => {
    // console.log(initialVNode, container);
    // 调用用户的 setup 函数执行，取得返回值，获取 render 函数返回的结果来进行渲染
    // 1. 创建实例
    const instance = (initialVNode.component = createComponentInstance(initialVNode));
    // 2. 把数据解析到实例上
    setupComponent(instance);
    // 3. 创建一个 effect 让用户的setup返回的函数执行返回 render 函数执行
    setupRenderEffect(instance, container);
  }

  const processComponent = (n1, n2, container) => {
    if (n1 == null) { // 组件没有上一次的虚拟 DOM
      mountComponent(n2, container);
    } else {
      // 组件更新流程
    }
  }
  //#endregion

  //#region 处理元素
  const mountChildren = (children, container) => {
    for (let i = 0; i < children.length; i++) {
      const child = normalizeVNode(children[i]);
      patch(null, child, container);
    }
  }

  const mountElement = (vnode, container, anchor = null) => {
    // 递归渲染
    const { props, shapeFlag, type, children } = vnode;
    const el = vnode.el = hostCreateElement(type);
    
    if (props) {
      for (const key in props) {
        hostPatchProp(el, key, null, props[key]);
      }
    }
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
      hostSetElementText(el, children);
    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
      mountChildren(children, el);
    }

    hostInsert(el, container, anchor);
  }

  // 新旧节点属性更新
  const patchProps = (oldProps, newProps, el) =>  {

    // 属性不一样
    if (oldProps !== newProps) {
      for (const key in newProps) {
        const prev = oldProps[key];
        const next = newProps[key];
        // 如果新的和老的属性不一样
        if (prev !== next) {
          hostPatchProp(el, key, prev, next);
        }
      }

      for (const key in oldProps) {
        // 老的有，新的没有
        if (!(key in newProps)) {
          hostPatchProp(el, key, oldProps[key], null);
        }
      }
    }
  }

  const unmountChildren = children => {
    for (let i = 0; i < children.length; i++) {
      unmount(children[i]);
    }
  }
  /**
   * 核心的 diff 比对 key
   * @param c1 老节点
   * @param c2 新节点
   * @param el 挂载的元素
   */
  const patchKeyedChildren = (c1, c2, el) => {
    let i = 0; // 默认从头开始比较，头指针
    let e1 = c1.length - 1; // 旧节点的尾指针
    let e2 = c2.length - 1; // 新节点的尾指针

    // 情况一：从头开始比较，遇到不同就停止 // a b c 与 a b d e
    while(i <= e1 && i <= e2) {
      const n1 = c1[i];
      const n2 = c2[i];

      // 是同一个元素
      if (isSameVNodeType(n1, n2)) {
        patch(n1, n2, el);
      } else {
        break; // 遇到不同就结束
      }
      i++;
    }

    // 情况二：从头结尾比较，遇到不同就停止 // a b c 与 d e b c
    while(i <= e1 && i <= e2) {
      const n1 = c1[e1];
      const n2 = c2[e2];
      // 是同一个元素
      if (isSameVNodeType(n1, n2)) {
        patch(n1, n2, el);
      } else {
        break; // 遇到不同就结束
      }
      e1--; // 旧节点的尾指针不断向前
      e2--; // 新节点的尾指针不前向前
    }

    // 有一方完全比对完
    if (i > e1) { // 老的少，新的多 a b c 与 a b c d, a b c 与 d a b c
      if (i <= e2) { // 标识有新增
        const nextPos = e2 + 1; // 获取新的下一个位置
        // 判断事向前插入，还是向后插入
        const anchor = nextPos < c2.length ? c2[nextPos].el : null;
        while(i <= e2) {
          patch(null, c2[i], el, anchor); // 向指定的 anchor 后追加
          i++;
        }
      }
    } else if (i > e2) { // 老的多，新的少 a b c d 与 a b c, d a b c 与 a b c
      // 老的多，新的少，就删除老的
      while(i <= e1) {
        unmount(c1[i]);
        i++
      }
    } else { // 比较乱， 尽可能复用，新的元素做一个映射表缓存
      // 比较乱，尽可能复用，新的元素做一个映射表缓存去老的里面寻找比对，一样就复用，不一样旧插入或者删除

      let s1 = i;
      let s2 = i;
      const keyToNewIndexMap = new Map();
      // 使用新的做缓存表，也可以使用老的做缓存表
      for (let i = s2; i <= e2; i++){
        const childVNode = c2[i];
        keyToNewIndexMap.set(childVNode.key, i);
      }

      // 去旧的里找，比对有没有复用的
      for (let i = s1; i <= e1; i++) {
        const oldVnode = c1[i];
        const newIndex = keyToNewIndexMap.get(oldVnode.key);
        if (newIndex === undefined) { // 老的不在新的节点中就删除老的
          unmount(oldVnode); // a b [c d e q] f g 与a b [e c d h] f g 这里 q 就被删除
        } else { // 新旧比对
          patch(oldVnode, c2[newIndex], el);
        }
      }

      /**
       * 最后是移动节点，并且新增的节点插入
       * 最长递增子序列算法
       */

    }

  }
  // 新老孩子比对
  const patchChildren = (n1, n2, el) => {
    const c1 = n1.children;
    const c2 = n2.children;

    /**
     * 四种情况：
     * 1. 老的有孩子，新的没有孩子
     * 2. 新的有孩子，老的没有孩子
     * 3. 新老都有孩子
     * 4. 新老的都是文本
     */

    const prevShapeFlag = n1.shapeFlag; // 旧的
    const shapeFlag = n2.shapeFlag; // 新的

    // 新的是文本，旧的是数组
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {

      // 旧的有多个孩子，新的就是一个文本，就删掉老的所有孩子
      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
         // 删掉老的所有孩子
        unmountChildren(c1); // 如果 c1 中包含组件需要调用组件的销毁方法
      }

      if (c2 !== c1) { // 新旧都是文本
        hostSetElementText(el, c2);
      }
    } else { // 新的是元素，旧的有可能是文本或数组
      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        
        // 新的是数组，旧的是数组
        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
          // 新的是数组，旧的是数组
          // diff 算法，两个数组的比对
          console.log('diff');
          patchKeyedChildren(c1, c2, el);
        } else {
          // 新的没有孩子（null），旧的是数组,删除掉旧的
          unmountChildren(c1);
        }

      } else {
        // 旧的是文本,新的是文本
        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
          hostSetElementText(el, '');
        }
        // 旧的是文本,新的是数组
        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
          mountChildren(c2, el);
        }
      }
    }
  }
  // 新旧元素更新
  const patchElement = (n1, n2, container) => {
    // 元素节点是相同的, 就更新属性和孩子
    const el = (n2.el = n1.el); // 元素节点是相同的，复用老的元素
    const oldProps = n1.props || {};
    const newProps = n2.props || {};
    // 更新属性
    patchProps(oldProps, newProps, el);

    // 更新孩子
    patchChildren(n1, n2, el);
  }

  const processElement = (n1, n2, container, anchor = null) => {
    if (n1 == null) {
      mountElement(n2, container, anchor);
    } else {
      // 元素更新
      console.log('元素更新',n1, n2)
      patchElement(n1, n2, container);
    }
  }
  //#endregion

  //#region 文本处理
  const processText = (n1, n2, container) => {
    if (n1 == null) {
      hostInsert(n2.el = hostCreateText(n2.children), container);
    } else {
      // 文本更新
    }
  }
  //#endregion

  /**
   * 判断两个节点是否一样
   * @param n1 旧节点
   * @param n2 新节点
   */
  const isSameVNodeType = (n1, n2) => {
    return n1.type === n2.type && n1.key === n2.key;
  }

  // n1 可能是组件也可能是元素，组件的声明周期等
  const unmount = n1 =>  {
    hostRemove(n1.el); // 删除旧节点
  }

  /**
   * 
   * @param n1 旧虚拟 DOM
   * @param n2 新虚拟 DOM
   * @param anchor 参考节点，需要插入的位置
   * @param container 容器
   */
  const patch = (n1, n2, container, anchor = null) => {
    const { shapeFlag, type } = n2;
    // 存在旧节点 n1, 如果两个节点不一样
    if (n1 && !isSameVNodeType(n1, n2)) {
      // 先找到需要插入的参照节点
      anchor = hostNextSibling(n1.el);
      // 1. 两个节点不一样,就删除旧节点
      unmount(n1);
      // 2. 渲染新节点
      n1 = null;  // 重新渲染 n2 节点 下面就执行 processElement
    }

    switch(type) {
      case Text:
        // console.log('文本', n1, n2, container);
        processText(n1, n2, container);
        break;
      default: 
        if (shapeFlag & ShapeFlags.ELEMENT) {
          // console.log('元素', n1, n2, container);
          processElement(n1, n2, container, anchor);
        } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
          // console.log('组件', n1, n2, container);
          processComponent(n1, n2, container);
        }
    }
  }

  const render = (vnode, container) => {
    // runtime-core 的核心
    patch(null, vnode, container);
  }

  return {
    createApp: createAppApi(render)
  }
 }